﻿/*
 * Created by SharpDevelop.
 */
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

using BrightIdeasSoftware;

namespace MiniMIPS
{
	/// <summary>
	/// Description of MainForm.
	/// </summary>
	/// 
	
	public class MIPSForm : Form
	{
		public virtual void Save(string filename)
		{
			
		}
	}
	public partial class MainForm : MIPSForm
	{
		OperationList opList;
		List<Instruction> insList;
		Dictionary<int, Color> jumpDict;
		List<Button> buttonList;
		Simulator sim;
		bool fileChanged;
		
		public MainForm()
		{
			//
			// The InitializeComponent() call is required for Windows Forms designer support.
			//
			InitializeComponent();
		
			opList = new OperationList();
			insList = new List<Instruction>();
			jumpDict = new Dictionary<int, Color>();
			buttonList = new List<Button>();
			
			SetupListView();
			RefreshListView(true);
			
			buttonList.Add(btnAdd);
			buttonList.Add(btnRemove);
			buttonList.Add(btnUp);
			buttonList.Add(btnDown);
			buttonList.Add(btnLoad);
			buttonList.Add(btnClear);
			
			scrollSpeed.Value = scrollSpeed.Maximum / 2;
			SimSpeedChange(scrollSpeed.Maximum / 2);
			
			this.MinimumSize = this.Size;
			SetFileName("untitled.mips");
		}
		
		string StripSlash(string str)
		{
			int index = str.LastIndexOf("\\");
			if(index >= 0)
			{
				str = str.Substring(index + 1);
			}
			return str;
		}
		
		void SetFileName(string file)
		{
			openFD.FileName = file;
			saveFD.FileName = file;

			this.Text = "MiniMIPS Sim - " + StripSlash(file);
			this.fileChanged = false;
		}
		
		void MainFormLoad(object sender, EventArgs e)
		{
		}
		
		#region ListView Methods
		
		void SetupListView()
		{
			this.colLine.AspectGetter = delegate(object x) {
				Instruction i = (Instruction)x;
				
				return insList.IndexOf(i);
			};
			this.colA.AspectGetter = delegate(object x) {
				Instruction i = (Instruction)x;
				
				if(i.Op.Type == OpType.RType ||
				   i.Op.Type == OpType.IType)
				{
					if(i.Op.Func == OpFunc.BranchEq ||
					   i.Op.Func == OpFunc.Store)
					{
						return (RegsS)i.Params[0];
					}
					else
					{
						return (RegsD)i.Params[0];
					}
				}
				else if(i.Op.Type == OpType.JType)
				{
					return (uint)i.Params[0];
				}
				else
				{
					return -1;
				}
			};
			this.colA.AspectPutter = delegate(object x, object y) {
				Instruction i = (Instruction)x;
				
				int val;
				if(i.Op.Type == OpType.JType)
				{
					val = (int)((uint)y);
				}
				else
				{
					val = (int)y;
				}
				
				i.Params[0] = val;
			};
			
			this.colB.AspectGetter = delegate(object x) {
				Instruction i = (Instruction)x;
			
				if(i.Op.Type == OpType.RType ||
				   i.Op.Type == OpType.IType)
				{
					return (RegsS)i.Params[1];
				}
				else if(i.Op.Type == OpType.JType)
				{
					return "";
				}
				else
				{
					return -1;
				}
			};
			this.colB.AspectPutter = delegate(object x, object y) {
				Instruction i = (Instruction)x;
				int val = (int)y;
				
				i.Params[1] = val;
			};
			
			this.colC.AspectGetter = delegate(object x) {
				Instruction i = (Instruction)x;
				
				if(i.Op.Type == OpType.RType)
				{
					return (RegsS)i.Params[2];
				}
				else if(i.Op.Type == OpType.IType)
				{
					return (int)i.Params[2];
				}
				else if(i.Op.Type == OpType.JType)
				{
					return "";
				}
				else
				{
					return -1;
				}
			};			
			this.colC.AspectPutter = delegate(object x, object y) {
				Instruction i = (Instruction)x;
				int val = (int)y;
				
				i.Params[2] = val;
			};
			

			this.colOp.AspectGetter = delegate(object x) {
				Instruction i = (Instruction)x;
				
				return (Op)opList.IndexOf(i.Op);
			};
			
			this.colOp.AspectPutter = delegate(object x, object y) {
				Instruction i = (Instruction)x;
				Op op = (Op)y;
				
				Operation newOp = opList[(int)op];				
				
				i.Op = newOp;
			};
			
			this.olvIns.RowFormatter = delegate(OLVListItem olvi) {
				Instruction i = (Instruction)olvi.RowObject;
				int index = insList.IndexOf(i);
								
				if(jumpDict.ContainsKey(index))
				{
					olvi.BackColor = jumpDict[index];
				}
				
				if(i.Op.Func == OpFunc.Jump)
				{
					int a = this.olvIns.Columns.IndexOf(this.colA);
					
					if(i.Params[0] > insList.Count - 1)
					{
						StyleSubItems(olvi);
           				olvi.SubItems[a].ForeColor = Color.White;
           				olvi.SubItems[a].BackColor = Color.Red;
					}
				}
				else if(i.Op.Func == OpFunc.BranchEq)
				{
					int c = this.olvIns.Columns.IndexOf(this.colC);
					int addr = i.Params[2] + index + 1;
					
					if(addr < 0 || addr > insList.Count - 1)
					{	
						StyleSubItems(olvi);
           				olvi.SubItems[c].ForeColor = Color.White;
           				olvi.SubItems[c].BackColor = Color.Red;
					}
				}

			};
		}
		
		void RefreshListView(bool reset)
		{
			jumpDict.Clear();
			
			int selected = olvIns.SelectedIndex;
			
			if(selected >= 0 && selected < insList.Count)
			{
				Instruction ins = insList[selected];
				
				if(ins.Op.Func == OpFunc.Jump)
				{
					jumpDict.Add(ins.Params[0], Color.LightGreen);
				}
				else if(ins.Op.Func == OpFunc.BranchEq)
				{
					int addr = ins.Params[2] + selected + 1;
					jumpDict.Add(addr, Color.LightGreen);
				}
			}

			if(reset)
			{
				olvIns.SetObjects(insList);
			}
			else
			{
				olvIns.RefreshObjects(insList);
				olvIns.Refresh();
				
				/*
				foreach(int i in jumpDict.Keys)
				{
					if(i >= 0 && i < insList.Count)
					{
						System.Diagnostics.Trace.WriteLine("Redraw " + i);
						olvIns.RedrawItems(i, i, false);
					}
				}
				*/

			}
		}
		
		void StyleSubItems(OLVListItem olvi)
		{
			olvi.UseItemStyleForSubItems = false;
			for(int i = 0; i < olvi.SubItems.Count; i++)
			{
				olvi.SubItems[i].ForeColor = olvi.ForeColor;
				olvi.SubItems[i].BackColor = olvi.BackColor;
			}
		}
		
		#endregion

		int LastSelected = 0;
		
		void OlvInsSelectedIndexChanged(object sender, EventArgs e)
		{
			System.Diagnostics.Trace.WriteLine(olvIns.SelectedIndex);
			
			if(olvIns.SelectedIndex >= 0 && btnReset.Enabled)
			{
				btnGoTo.Enabled = true;
			}
			else
			{
				btnGoTo.Enabled = false;
			}
			
			if(olvIns.SelectedIndex >= 0)
			{
				LastSelected = olvIns.SelectedIndex;
				Instruction ins = (Instruction)olvIns.SelectedObject;
				
				if(ins.Op.Type == OpType.RType)
				{
					colA.Text = "Rd";
					colB.Text = "Rs";
					colC.Text = "Rt";
				}
				else if(ins.Op.Type == OpType.IType)
				{
					colA.Text = "Rt";
					colB.Text = "Rs";
					colC.Text = "Imm";
				}
				else if(ins.Op.Type == OpType.JType)
				{
					colA.Text = "Addr";
				}
			}
			else
			{
					colA.Text = "";
					colB.Text = "";
					colC.Text = "";
			}
			
			RefreshListView(false);
		}
		
		void BtnAddClick(object sender, EventArgs e)
		{
			List<int> l = new List<int>();
			l.Add(1);
			for(int i = 1; i < 3; i++)
			{
				l.Add(0);
			}
			
			Instruction tmp = new Instruction(opList[0], l);
			int index = olvIns.SelectedIndex;
			
			if(index >= 0)
			{
				insList.Insert(index, tmp);
			}
			else
			{
				insList.Add(tmp);
			}
			
			fileChanged = true;
			olvIns.SetObjects(insList);
			olvIns.SelectedIndex = index;
		}
		
		void BtnUpClick(object sender, EventArgs e)
		{
			int index = olvIns.SelectedIndex;
			
			if(index >= 1)
			{
				Instruction tmp = insList[index];
				insList[index] = insList[index - 1];
				insList[index - 1] = tmp;
				
				fileChanged = true;
				RefreshListView(true);
				olvIns.SelectedIndex = index - 1;
			}
		}
		
		void BtnDownClick(object sender, EventArgs e)
		{
			int index = olvIns.SelectedIndex;
			
			if(index >= 0 && index < insList.Count - 1)
			{
				Instruction tmp = insList[index];
				insList[index] = insList[index + 1];
				insList[index + 1] = tmp;
				
				fileChanged = true;
				RefreshListView(true);
				olvIns.SelectedIndex = index + 1;
			}
			
		}
		
		void BtnRemoveClick(object sender, EventArgs e)
		{
			int index = olvIns.SelectedIndex;
			
			if(index >= 0)
			{
				insList.RemoveAt(index);
				fileChanged = true;
			}
			
			RefreshListView(true);
			
			if(index < insList.Count)
			{
				olvIns.SelectedIndex = index;
			}
			else
			{
				olvIns.SelectedIndex = insList.Count - 1;
			}
			
		}
		
		void OlvInsItemsChanged(object sender, ItemsChangedEventArgs e)
		{
			System.Diagnostics.Trace.WriteLine("ItemsChanged");
			RefreshListView(false);
		}
		
		void OlvInsCellEditFinishing(object sender, CellEditEventArgs e)
		{
			System.Diagnostics.Trace.WriteLine("CellEditFinished");
			fileChanged = true;
			RefreshListView(false);
			//refreshTimer.Enabled = true;
		}
		
		Instruction insEditing;
		
		void OlvInsCellEditStarting(object sender, CellEditEventArgs e)
		{
			System.Diagnostics.Trace.WriteLine("CellEditStarting");
			
			Instruction ins = (Instruction)e.RowObject;
			
			if(ins.Op.Type == OpType.JType && (e.Column == colB || e.Column == colC))
			{
				e.Cancel = true;
			}
			
			//limit numeric values for jumps and immediates
			if(ins.Op.Type == OpType.JType && e.Column == colA)
			{
				NumericUpDown ctrl = (NumericUpDown)e.Control;
				ctrl.Maximum = 127;
				insEditing = ins;
				ctrl.ValueChanged += new EventHandler(ctrl_AValueChanged);
			}
			else if(ins.Op.Type == OpType.IType && e.Column == colC)
			{
				NumericUpDown ctrl = (NumericUpDown)e.Control;
				ctrl.Maximum = 31;
				ctrl.Minimum = -32;
				insEditing = ins;
				ctrl.ValueChanged += new EventHandler(ctrl_CValueChanged);
			}
			else if(e.Control is ComboBox)
			{
				ComboBox ctrl = (ComboBox)e.Control;
				insEditing = ins;
				if(e.Column == colOp)
				{
					ctrl.SelectedValueChanged += new EventHandler(ctrl_OpSelectedValueChanged);
				}
				else if(e.Column == colA)
				{
					ctrl.SelectedValueChanged += new EventHandler(ctrl_ASelectedValueChanged);					
				}
				else if(e.Column == colB)
				{
					ctrl.SelectedValueChanged += new EventHandler(ctrl_BSelectedValueChanged);
				}
				else if(e.Column == colC)
				{
					ctrl.SelectedValueChanged += new EventHandler(ctrl_CSelectedValueChanged);
				}
			}
		}

		void ctrl_AValueChanged(object sender, EventArgs e)
		{
			NumericUpDown ctrl = (NumericUpDown)sender;
			insEditing.Params[0] = (int)ctrl.Value;
			RefreshListView(false);
		}

		void ctrl_CValueChanged(object sender, EventArgs e)
		{
			NumericUpDown ctrl = (NumericUpDown)sender;
			insEditing.Params[2] = (int)ctrl.Value;
			RefreshListView(false);
		}
		
		void ctrl_OpSelectedValueChanged(object sender, EventArgs e)
		{
			ComboBox ctrl = (ComboBox)sender;			
			Operation newOp = opList[(int)ctrl.SelectedValue];
			
			insEditing.Op = newOp;
						
			RefreshListView(false);
		}

		void ctrl_ASelectedValueChanged(object sender, EventArgs e)
		{
			ComboBox ctrl = (ComboBox)sender;			
			insEditing.Params[0] = (int)ctrl.SelectedValue;
						
			RefreshListView(false);
		}
				
		void ctrl_BSelectedValueChanged(object sender, EventArgs e)
		{
			ComboBox ctrl = (ComboBox)sender;			
			insEditing.Params[1] = (int)ctrl.SelectedValue;
				
			RefreshListView(false);
		}
		
		void ctrl_CSelectedValueChanged(object sender, EventArgs e)
		{
			ComboBox ctrl = (ComboBox)sender;			
			insEditing.Params[2] = (int)ctrl.SelectedValue;
				
			RefreshListView(false);
		}
								
		void BtnSaveClick(object sender, EventArgs e)
		{
			DialogResult dr = saveFD.ShowDialog();
			
			if(dr == DialogResult.OK)
			{
				SetFileName(saveFD.FileName);
				Save(saveFD.FileName);
			}
		}
		
		public override void Save(string filename)
		{
				BinaryFormatter binForm = new BinaryFormatter();
				
				try
				{
					FileStream fs;
					fs = new FileStream(filename, FileMode.Create);
				
					binForm.Serialize(fs, insList);
					fs.Close();
				}
				catch(Exception ex)
				{
					MessageBox.Show(ex.Message.ToString(), "Error Saving File!", 
					                MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
		}
		
		void BtnLoadClick(object sender, EventArgs e)
		{
			DialogResult dr = openFD.ShowDialog();
			
			if(dr == DialogResult.OK)
			{
				BinaryFormatter binForm = new BinaryFormatter();
				
				try
				{
					FileStream fs;
					fs = new FileStream(openFD.FileName, FileMode.Open);
				
					insList = (List<Instruction>)binForm.Deserialize(fs);
					fs.Close();
					
					SetFileName(openFD.FileName);
				}
				catch(Exception ex)
				{
					MessageBox.Show(ex.Message.ToString(), "Error Loading File!", 
					                MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
				
				RefreshListView(true);
			}
			
			for(int i = 0; i <insList.Count; i++)
			{
				System.Diagnostics.Trace.WriteLine(insList[i].Op.Name);
			}
		}
		

		void BtnStartClick(object sender, System.EventArgs e)
		{
			olvIns.CellEditActivation = ObjectListView.CellEditActivateMode.None;
			btnReset.Enabled = true;
			btnStart.Enabled = false;
			btnRun.Enabled = true;
			btnStep.Enabled = true;
			nudSWs.Enabled = true;
			
			foreach(Button b in buttonList)
			{
				b.Enabled = false;
			}
			
			sim = new Simulator(insList);
			DisplaySim(true);
		}
		
		
		
		void BtnResetClick(object sender, EventArgs e)
		{
			olvIns.CellEditActivation = ObjectListView.CellEditActivateMode.SingleClick;
			btnReset.Enabled = false;
			btnStart.Enabled = true;
			btnRun.Enabled = false;
			btnPause.Enabled = false;
			btnStep.Enabled = false;
			nudSWs.Enabled = false;
			
			foreach(Button b in buttonList)
			{
				b.Enabled = true;
			}
			
			simTimer.Enabled = false;
		}
		
		
		void BtnRunClick(object sender, EventArgs e)
		{
			btnRun.Enabled = false;
			btnPause.Enabled = true;
			btnStep.Enabled = false;
			simTimer.Enabled = true;
			olvIns.Focus();
		}
		
		void BtnPauseClick(object sender, EventArgs e)
		{
			btnRun.Enabled = true;
			btnPause.Enabled = false;
			btnStep.Enabled = true;
			simTimer.Enabled = false;
			olvIns.Focus();
		}
		
		void ScrollSpeedScroll(object sender, ScrollEventArgs e)
		{
			SimSpeedChange(scrollSpeed.Value);
		}
		
		void SimSpeedChange(int val)
		{
			lblSpeed.Text = "Sim Speed " + val;
			simTimer.Interval = 4000 / val;
		}
		
		void BtnStepClick(object sender, EventArgs e)
		{
			Status s = sim.Step();
			if(s != Status.Ready)
			{
				MessageBox.Show("Simulation ended with status: " + Enum.GetName(typeof(Status), s));
			}
							
			DisplaySim(true);
		}
		
		void DisplaySim(bool focus)
		{
			lblPC.Text = "PC = " + sim.PC;
			
			lbRegisters.BeginUpdate();
			lbRegisters.Items.Clear();
			for(int i = 0; i < sim.Registers.Count - 1; i++)
			{
				Register r = sim.Registers[i];
				if(r.UValue != r.Value)
				{
					lbRegisters.Items.Add("$r" + i.ToString() + " = " + r.UValue + " (" + r.Value + ")");
				}
				else
				{
					lbRegisters.Items.Add("$r" + i.ToString() + " = " + r.Value);
				}
			
			}
			
			if(sim.SWs != Int8.Unsigned(sim.SWs))
			{
				lbRegisters.Items.Add("$SWs = " + Int8.Unsigned(sim.SWs) + " (" + sim.SWs + ")");
			}
			else
			{
				lbRegisters.Items.Add("$SWs = " + sim.SWs.ToString());
			}
			if(sim.LEDs != Int8.Unsigned(sim.LEDs))
			{
				lbRegisters.Items.Add("$LEDs = " + Int8.Unsigned(sim.LEDs) + " (" + sim.LEDs + ")");
			}
			else
			{
				lbRegisters.Items.Add("$LEDs = " + sim.LEDs.ToString());
			}
			lbRegisters.EndUpdate();
			
			lbMemory.BeginUpdate();
			lbMemory.Items.Clear();
			foreach(int key in sim.Memory.Keys)
			{
				int sval = sim.Memory[key];
				int uval = Int8.Unsigned(sval);
				if(sval != uval)
				{
					lbMemory.Items.Add("M[" + key.ToString().PadLeft(3,'0') + "] = " + uval + " (" + sval + ")");
				}
				else
				{
					lbMemory.Items.Add("M[" + key.ToString().PadLeft(3,'0') + "] = " + sval);
				}

			}
			lbMemory.EndUpdate();
			
			olvIns.SelectedIndex = sim.PC;
			if(focus)
			{
				olvIns.Focus();
			}
			
			nudLEDs.Value = Int8.Unsigned(sim.LEDs);
			nudSWs.Value = Int8.Unsigned(sim.SWs);
			lblLEDs.Text = BinaryString.FromInt(sim.LEDs,8);
			lblSWs.Text = BinaryString.FromInt(sim.SWs,8);
		}
		
		void SimTimerTick(object sender, EventArgs e)
		{
			Status s = sim.Step();
			DisplaySim(false);
			
			if(s != Status.Ready)
			{
				btnPause.PerformClick();
				MessageBox.Show("Simulation ended with status: " + Enum.GetName(typeof(Status), s));			
			}
		}
		
		void BtnGoToClick(object sender, EventArgs e)
		{
			int index = olvIns.SelectedIndex;
			
			if(index >= 0)
			{
				Status s = Status.Ready;
				if(sim.PC == index)
				{
					s = sim.Step();
				}
				
				int count = 0;
				int max = 10000;
				while(s == Status.Ready && sim.PC != index && count < max)
				{
					s = sim.Step();
					count++;
				}
				
				if(count == max)
				{
					MessageBox.Show("Not reached after " + max + " cycles");
				}
				
				if(s != Status.Ready)
				{
					MessageBox.Show("Simulation ended with status: " + Enum.GetName(typeof(Status), s));
				}
				
				DisplaySim(true);		
				
			}
		}
		
		void BtnExcClick(object sender, EventArgs e)
		{
			throw new Exception("Test exception");
		}
		
		
		void NudSWsValueChanged(object sender, EventArgs e)
		{
			sim.SWs = (int)nudSWs.Value;
			lblSWs.Text = BinaryString.FromInt(sim.SWs,8);
		}
				
		void BtnExportClick(object sender, System.EventArgs e)
		{
			DialogResult dr = exportFD.ShowDialog();
			
			if(dr != DialogResult.OK)
			{
				return;
			}
			
			try
			{
				TextReader textReader = new StreamReader("template.vhdt");
				TextWriter textWriter = new StreamWriter(exportFD.FileName, false);
				
				string line = null;
				while((line = textReader.ReadLine()) != null)
				{
					if(line.Contains("%TITLE%"))
					{
						Version ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
						string verString = ver.Major + "." + ver.Minor;
						textWriter.WriteLine("-- " + StripSlash(exportFD.FileName) + " - " + StripSlash(saveFD.FileName));
						textWriter.WriteLine("-- Created on " + DateTime.Now.ToShortDateString() + " by MiniMIPS Sim " + verString);
					}
					else if(line.Contains("%COMMENTS%"))
					{
						foreach(Instruction ins in insList)
						{
							textWriter.WriteLine("-- " + ins.ToString().PadRight(30,' ') + " # " + ins.Comments);
						}
					}
					else if(line.Contains("%INSTRUCTIONS%"))
					{
						int indent = line.IndexOf("%INSTRUCTIONS%");
						
						Instruction ins;
						if(insList.Count > 0)
						{
							ins = insList[0];
							textWriter.WriteLine(line.Replace("%INSTRUCTIONS%", ins.VHDL) + ",  -- " + ins.ToString());
						}
						for(int i = 1; i < insList.Count; i++)
						{
							ins = insList[i];
							string vhdl = ins.VHDL;
							textWriter.WriteLine(vhdl.PadLeft(vhdl.Length + indent,' ') + ",  -- " + ins.ToString());
						}
					}
					else
					{
						textWriter.WriteLine(line);
					}
				}
				
				textReader.Close();
				textWriter.Close();
			}
			catch(Exception ex)
			{
				MessageBox.Show(ex.Message.ToString(), "Error Exporting File!", 
					            MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}
		
		void MainFormFormClosing(object sender, FormClosingEventArgs e)
		{
			if(!fileChanged)
			{
				return;
			}
			
			DialogResult dr = MessageBox.Show("Save changes before exit?", 
			                                  "Save Before Exit",
							                  MessageBoxButtons.YesNo,
							                  MessageBoxIcon.Exclamation);
			
			if(dr == DialogResult.Yes)
			{
				btnSave.PerformClick();
			}
	
		}
		
		void BtnClearClick(object sender, EventArgs e)
		{
			DialogResult dr = MessageBox.Show("Clear all?", "Clear",
			                				  MessageBoxButtons.YesNo,
			                				  MessageBoxIcon.Exclamation);
			
			if(dr == DialogResult.Yes)
			{
				insList.Clear();
				RefreshListView(true);
				SetFileName("untitled.mips");
			}
		}
		
		void BtnAboutClick(object sender, EventArgs e)
		{
			Form about = new AboutForm();
			about.ShowDialog();
		}
	}
}
